package com.mjk.manager.magicapi.core.core.resource;

import com.mjk.manager.magicapi.core.core.model.MagicEntity;
import com.mjk.manager.magicapi.core.utils.Assert;
import com.mjk.manager.magicapi.core.utils.IoUtils;
import com.mjk.manager.magicapi.core.utils.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;


import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.mjk.manager.magicapi.core.core.service.MagicResourceStorage.separatorWithCRLF;

/**
 * 数据库资源存储
 *
 * @author mxd
 */
public class DatabaseResource extends KeyValueResource {

    private static final Logger logger = LoggerFactory.getLogger(DatabaseResource.class);
    private final JdbcTemplate template;
    private final String tableName;
    private Map<String, String> cachedContent = new ConcurrentHashMap<>();

    public DatabaseResource(JdbcTemplate template, String tableName) {
        this(template, tableName, false);
    }

    public DatabaseResource(JdbcTemplate template, String tableName, boolean readonly) {
        this(template, tableName, "/magic-api", readonly);
    }

    public DatabaseResource(JdbcTemplate template, String tableName, String path, boolean readonly) {
        this(template, tableName, path, readonly, null);
    }

    public DatabaseResource(JdbcTemplate template, String tableName, String path, boolean readonly, KeyValueResource parent) {
        super("/", path, readonly, parent);
        this.template = template;
        this.tableName = tableName;
    }

    public DatabaseResource(JdbcTemplate template, String tableName, String path, boolean readonly, Map<String, String> cachedContent, KeyValueResource parent) {
        this(template, tableName, path, readonly, parent);
        this.cachedContent = cachedContent;
    }

    @Override
    public byte[] read() {
        String value = this.cachedContent.get(path);
        if (value == null) {
            String sql = String.format("select file_content from %s where file_path = ?", tableName);
            value = template.queryForObject(sql, String.class, this.path);
            if (value != null) {
                this.cachedContent.put(path, value);
            }
        }
        return value == null ? new byte[0] : value.getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public void readAll() {
        this.cachedContent.entrySet().removeIf(entry -> entry.getKey().startsWith(path));
        String sql = String.format("select file_path, file_content from %s where file_path like '%s%%'", tableName, this.path);
        SqlRowSet sqlRowSet = template.queryForRowSet(sql);
        while (sqlRowSet.next()) {
            Object object = sqlRowSet.getObject(2);
            String content = null;
            if (object instanceof String) {
                content = object.toString();
            } else if (object instanceof byte[]) {
                content = new String((byte[]) object, StandardCharsets.UTF_8);
            } else if (object instanceof Blob) {
                Blob blob = (Blob) object;
                try (InputStream is = blob.getBinaryStream()) {
                    content = new String(IoUtils.bytes(is), StandardCharsets.UTF_8);
                } catch (SQLException | IOException ex) {
                    logger.error("读取content失败", ex);
                }
            } else if (object instanceof Clob) {
                Clob clob = (Clob) object;
                try {
                    content = clob.getSubString(1, (int) clob.length());
                } catch (SQLException ex) {
                    logger.error("读取content失败", ex);
                }
            }
            Assert.isNotNull(content, "读取content失败，请检查列类型是否正确");
            this.cachedContent.put(sqlRowSet.getString(1), content);
        }
    }

    @Override
    public boolean exists() {
        if (this.cachedContent.get(this.path) != null) {
            return true;
        }
        String sql = String.format("select count(*) from %s where file_path = ?", tableName);
        Long value = template.queryForObject(sql, Long.class, this.path);
        return value != null && value > 0;
    }

    @Override
    public boolean write(String content) {
        String sql = String.format("update %s set file_content = ? where file_path = ?", tableName);
        if (exists()) {
            if (template.update(sql, content, this.path) > 0) {
                this.cachedContent.put(this.path, content);
                return true;
            }
        }
        sql = String.format("insert into %s (file_path,file_content) values(?,?)", tableName);
        if (template.update(sql, this.path, content) > 0) {
            this.cachedContent.put(this.path, content);
            return true;
        }
        return false;
    }
    @Override
    public boolean write(MagicEntity entity) {
        String sql = String.format("update %s set file_content = ?,parent_path=?,path=?,liteComp=? where file_path = ?", tableName);
        entity = entity.copy();
        String script = entity.getScript();
        entity.setScript(null);
        String content = JsonUtils.toJsonString(entity) + separatorWithCRLF + script;
        if (exists()) {
            if (template.update(sql, content,entity.getGroupName(),entity.getePath(),entity.getGroupName()+"@"+entity.getePath(),this.path) > 0) {
                this.cachedContent.put(this.path, content);
                return true;
            }
        }
        sql = String.format("insert into %s (file_path,file_content,parent_path,path,liteComp) values(?,?,?,?,?)", tableName);
        if (template.update(sql, this.path, content,entity.getGroupName(),entity.getGroupName()+"@"+entity.getePath(),entity.getePath()) > 0) {
            this.cachedContent.put(this.path, content);
            return true;
        }
        return false;
    }
    @Override
    public Set<String> keys() {
        String prefix = isDirectory() ? this.path : (this.path + separator);
        if (!cachedContent.isEmpty()) {
            return cachedContent.keySet().stream().filter(it -> it.startsWith(prefix)).collect(Collectors.toSet());
        }
        String sql = String.format("select file_path from %s where file_path like '%s%%'", tableName, prefix);
        return new HashSet<>(template.queryForList(sql, String.class));
    }

    @Override
    public boolean renameTo(Map<String, String> renameKeys) {
        List<Object[]> args = renameKeys.entrySet().stream().map(entry -> new Object[]{entry.getValue(), entry.getKey()}).collect(Collectors.toList());
        String sql = String.format("update %s set file_path = ? where file_path = ?", tableName);
        int affectedRows = Arrays.stream(template.batchUpdate(sql, args)).sum();
        //oracle 执行更新操作成功会返回-2
        if (affectedRows > 0 || affectedRows == -2) {
            renameKeys.forEach((oldKey, newKey) -> this.cachedContent.put(newKey, this.cachedContent.remove(oldKey)));
            return true;
        }
        return false;
    }

    @Override
    public boolean delete() {
        String sql = String.format("delete from %s where file_path = ? or file_path like '%s%%'", tableName, path);
        if (template.update(sql, this.path) > 0) {
            this.cachedContent.entrySet().removeIf(entry -> entry.getKey().startsWith(path));
            return true;
        }
        return false;
    }

    @Override
    public Function<String, Resource> mappedFunction() {
        return it -> new DatabaseResource(template, tableName, it, readonly, this.cachedContent, this);
    }

    @Override
    public String toString() {
        return String.format("db://%s/%s", tableName, Objects.toString(this.path, ""));
    }

}
